﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using WPCoco.Tasks;
using WPCoco.Helpers;

namespace WPCoco
{
    /// <summary>
    /// Base class of all WPCoco drawing hierachy
    /// </summary>
    public class Node : IDisposable, ITaskScheduler, IContainer<Node>
    {
        Node owner;
        /// <summary>
        /// Parent of this node
        /// </summary>
        public Node Owner
        {
            get { return owner; }
            protected set 
            { 
                owner = value;
                if (owner == null)
                {
                    IsRunning = false;
                    Unlinked();
                }
                else
                {
                    IsRunning = owner.IsRunning;
                    Linked();
                }
            }
        }

        public void RemoveFromParent() 
        {
            if (owner == null) return;
            owner.RemoveNode(this, true);
        }

        protected virtual void Linked() { }

        protected virtual void Unlinked() { }

        int zOrder;
        /// <summary>
        /// Drawing order of the node among its siblings
        /// </summary>
        public int ZOrder
        {
            get { return zOrder; }
            set 
            { 
                if (zOrder==value) return;
                zOrder = value;
                if (owner != null) owner.ReorderChild(this);
            }
        }

        bool needUpdate;
        /// <summary>
        /// Allow the call to the Update method for adding some specific functionality
        /// </summary>
        public bool NeedUpdate
        {
            get { return needUpdate; }
            set { needUpdate = value; }
        }

        bool visible = true;
        /// <summary>
        /// Allow the call to the draw method
        /// </summary>
        public virtual bool Visible
        {
            get { return visible; }
            set 
            {
                if (visible == value) return;
                visible = value;
                UpdateDrawed();
                VisibilityChange();
            }
        }

        protected virtual void VisibilityChange() { }

        bool drawed = false;
        /// <summary>
        /// If its true this node has been drawed in this moment
        /// </summary>
        public bool IsDrawed
        {
            get { return drawed; }
        }

        void UpdateDrawed()
        {
            bool value = visible && running;
            if (owner != null)
                value = value && owner.drawed;
            if (drawed == value) return;
            drawed = value;
            for (int i = 0; i < childs.Count; i++)
                childs[i].UpdateDrawed();
        }
        
        bool running = false;
        /// <summary>
        /// If its true this node has been linked to a running hierachy
        /// </summary>
        public bool IsRunning 
        {
            get { return running; }
            protected set 
            {
                if (running == value) return;
                running = value;
                RaiseIsRunningChange();
                UpdateDrawed();
                Node n;
                for (int i = 0; i < ChildCount; i++)
                {
                    n = childs[i];
                    n.IsRunning = value;
                }
            }
        }

        /// <summary>
        /// Attached object to this node
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// Key for this node
        /// </summary>
        public int Key { get; set; }

        readonly List<Node> childs = new List<Node>();

        /// <summary>
        /// Return the index-th child node
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public Node this[int index]
        {
            get { return childs[index]; }
        }

        /// <summary>
        /// Numbers of children nodes
        /// </summary>
        public int ChildCount { get { return childs.Count; } }

        /// <summary>
        /// For adding one node at the specified zOrder
        /// </summary>
        /// <param name="node">Node to be add</param>
        /// <param name="zOrder">Z-Order of drawing</param>
        public virtual void AddNode(Node node, int zOrder) 
        {
            node.ZOrder = zOrder;
            AddNode(node);
        }

        /// <summary>
        /// For adding one node
        /// </summary>
        /// <param name="node">Node to be add</param>
        public virtual void AddNode(Node node) 
        {
            int index = FindNode(node);
            if (index != -1) return;
            int i;
            for (i = 0; i < childs.Count; i++)
                if (node.zOrder < childs[i].zOrder) break;
            childs.Insert(i, node);
            node.Owner = this;
            HasBeenAdded(node);
        }

        protected virtual void HasBeenAdded(Node node) { }

        /// <summary>
        /// For removing one specified node
        /// </summary>
        /// <param name="node">Node to be remove</param>
        public virtual void RemoveNode(Node node)
        {
            int index = FindNode(node);
            if (index == -1) return;
            childs.RemoveAt(index);
            node.Owner = null;
            HasBeenRemoved(node);
        }

        protected virtual void HasBeenRemoved(Node node) { }
        
        /// <summary>
        /// For removing and stoping all task attached to this node
        /// </summary>
        /// <param name="node">Node to be remove</param>
        /// <param name="cleanUp">If true, should be stoped all task attached to this node</param>
        public virtual void RemoveNode(Node node, bool cleanUp)
        {
            RemoveNode(node);
            if (cleanUp) 
                Scheduler.Shared.RemoveFromOwner(node);
        }

        protected void ReorderChild(Node node)
        {
            int index = FindNode(node);
            if (index == -1) return;
            childs.RemoveAt(index);
            int i;
            for (i = 0; i < childs.Count; i++)
                if (node.zOrder < childs[i].zOrder) break;
            childs.Insert(i, node);
        }

        /// <summary>
        /// Remove all nodes and allow to stop all running tasks
        /// </summary>
        /// <param name="cleanUp">True if the task should be stoped</param>
        public void RemoveAll(bool cleanUp) 
        {
            for (int i = 0; i < ChildCount; i++)
            {
                if (cleanUp)
                    Scheduler.Shared.RemoveFromOwner(childs[i]);
                childs[i].Owner = null;
                HasBeenRemoved(childs[i]);
            }
            childs.Clear();
        }

        /// <summary>
        /// Return index of node of its owned, else return -1
        /// </summary>
        /// <param name="node">Node to find</param>
        /// <returns>Index of the node</returns>
        public int FindNode(Node node)
        {
            //HACK: do this with a binary search by zorder
            for (int i = 0; i < childs.Count; i++)
                if (childs[i] == node) return i;
            return -1;
        }

        #region Drawing Stuff

        public virtual void Draw(GameTime gameTime) 
        {
            BeginDraw(gameTime);

            Node n;
            int i;
            for (i = 0; i < childs.Count; i++)
            {
                n = childs[i];
                if (n.zOrder < 0)
                {
                    if (n.visible)
                        n.Draw(gameTime);
                }
                else break;
            }

            SelfDraw(gameTime);

            for (; i < childs.Count; i++)
            {
                n = childs[i];
                if (n.visible)
                    n.Draw(gameTime);
            }

            EndDraw(gameTime);
        }

        protected virtual void BeginDraw(GameTime gameTime) 
        {

        }

        public virtual void SelfDraw(GameTime gameTime)
        {

        }

        protected virtual void EndDraw(GameTime gameTime)
        {

        }

        #endregion

        #region Updating Stuff

        public virtual void Update(GameTime gameTime)
        {
            if (needUpdate)
                SelfUpdate(gameTime);
            for (int i = 0; i < childs.Count; i++)
                childs[i].Update(gameTime);
        }

        protected virtual void SelfUpdate(GameTime gameTime) 
        {

        }

        #endregion

        #region Events

        public event EventHandler IsRunningChange;

        void RaiseIsRunningChange() 
        {
            if (IsRunningChange == null) return;
            IsRunningChange(this, EventArgs.Empty);
        }

        #endregion

        #region IDisposable Members

        bool isDisposed = false;

        public virtual void Dispose()
        {
            if (isDisposed) return;
            RemoveAll(true);
            isDisposed = true;
        }

        #endregion

        #region ITaskScheduler Members

        public void RunTask(Task task)
        {
            task.Owner = this;
            TaskHelper.ExpandTarget(task, this);
            Scheduler.Shared.AddTask(task);
        }

        public void StopTasks()
        {
            Scheduler.Shared.RemoveFromOwner(this);
        }

        #endregion

        #region IContainer<Node> Members

        public IEnumerable<Node> GetContainerEnumerator()
        {
            for (int i = 0; i < ChildCount; i++)
                yield return childs[i];
            yield break;
        }

        #endregion
    }
}
